package controller

import (
	"fmt"
	"runtime/debug"
	"strings"
	"sync"

	"go.uber.org/zap"

	"v8s-controller/internal/db"
)

type (
	generic struct {
		log *zap.SugaredLogger
	}

	controllerHandlingResult struct {
		handled bool
		err     error
	}
)

func (g *generic) withChannel(channel <-chan db.ProjectResult, block func(*db.Project) (bool, error)) error {
	results := make(chan controllerHandlingResult, 100)

	wg := sync.WaitGroup{}
	wg.Add(1)

	handledCount := 0
	var err error

	go func() {
		defer func() {
			recovered := recover()
			if recovered != nil {
				debug.PrintStack()
				g.log.Errorf("error happend while building errors: %s", recovered)
			}
		}()
		errors := make([]error, 0)

		for {
			result, more := <-results
			if !more {
				break
			}

			if result.err != nil {
				errors = append(errors, result.err)
				continue
			}

			if result.handled {
				handledCount++
				continue
			}
		}

		if len(errors) > 0 {
			builder := strings.Builder{}

			builder.WriteString("failed to handle projects:\n")

			for _, current := range errors {
				builder.WriteString("  ")
				builder.WriteString(current.Error())
				builder.WriteString("\n")
			}

			err = fmt.Errorf(builder.String())
		}

		wg.Done()
	}()

	subscription := sync.WaitGroup{}
	subscription.Add(1)
	workers := sync.WaitGroup{}
	for {
		result, more := <-channel
		if !more {
			g.log.Infof("no projects left")
			subscription.Done()
			break
		}

		workers.Add(1)

		go func() {
			defer func() {
				recovered := recover()
				if recovered != nil {
					debug.PrintStack()
					g.log.Errorf("error happend while handling result: %s", recovered)

				}

				workers.Done()
			}()

			g.handleResult(result, block, results)
		}()
	}

	subscription.Wait()
	workers.Wait()
	close(results)
	wg.Wait()

	if handledCount > 0 {
		g.log.Infof("handled '%d' projects", handledCount)
	}

	return err
}

func (g *generic) handleResult(result db.ProjectResult, block func(*db.Project) (bool, error), results chan controllerHandlingResult) {
	if result.Error != nil {
		results <- controllerHandlingResult{err: fmt.Errorf("couldn't extract project from result: %s", result.Error)}
		return
	}

	handled, err := block(result.Project)
	if err != nil {
		results <- controllerHandlingResult{err: fmt.Errorf("failed to handle project '%s': %s", result.Project.ID, err)}
		return
	}

	if handled {
		g.log.Infof("project '%s' handled", result.Project.ID)
	}

	results <- controllerHandlingResult{handled: handled}
}
